#include "wx/wxprec.h"
#include "wx/apptrait.h"
#include "wx/datetime.h"
#include "wx/file.h"
#include "wx/msgout.h"
#include "wx/textfile.h"
#include "wx/thread.h"
#include "wx/wxchar.h"
#include <errno.h>
#include <stdlib.h>
#include <time.h>

#ifdef  LOG_PRETTY_WRAP
static void wxLogWrap( FILE *f, const char *pszPrefix, const char *psz );
#endif

void wxVLogGeneric( wxLogLevel level, const wxChar *szFormat, va_list argptr ) {
  if( wxLog::IsEnabled() ) {
    wxLog::OnLog( level, wxString::FormatV( szFormat, argptr ), time( NULL ) );
  }
}

void wxLogGeneric( wxLogLevel level, const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogGeneric( level, szFormat, argptr );
  va_end( argptr );
}

#define IMPLEMENT_LOG_FUNCTION(level)                               \
  void wxVLog##level(const wxChar *szFormat, va_list argptr)        \
  {                                                                 \
    if ( wxLog::IsEnabled() ) {                                     \
      wxLog::OnLog(wxLOG_##level,                                   \
                   wxString::FormatV(szFormat, argptr), time(NULL));\
    }                                                               \
  }                                                                 \
  \
  void wxLog##level(const wxChar *szFormat, ...)                    \
  {                                                                 \
    va_list argptr;                                                 \
    va_start(argptr, szFormat);                                     \
    wxVLog##level(szFormat, argptr);                                \
    va_end(argptr);                                                 \
  }

IMPLEMENT_LOG_FUNCTION( Error )
IMPLEMENT_LOG_FUNCTION( Warning )
IMPLEMENT_LOG_FUNCTION( Message )
IMPLEMENT_LOG_FUNCTION( Info )
IMPLEMENT_LOG_FUNCTION( Status )

void wxSafeShowMessage( const wxString& title, const wxString& text ) {
  wxFprintf( stderr, _T( "%s: %s\n" ), title.c_str(), text.c_str() );
  fflush( stderr );
}

void wxVLogFatalError( const wxChar *szFormat, va_list argptr ) {
  wxSafeShowMessage( _T( "Fatal Error" ), wxString::FormatV( szFormat, argptr ) );
  abort();
}

void wxLogFatalError( const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogFatalError( szFormat, argptr );
}

void wxVLogVerbose( const wxChar *szFormat, va_list argptr ) {
  if( wxLog::IsEnabled() ) {
    if( wxLog::GetActiveTarget() != NULL && wxLog::GetVerbose() ) {
      wxLog::OnLog( wxLOG_Info,
                    wxString::FormatV( szFormat, argptr ), time( NULL ) );
    }
  }
}

void wxLogVerbose( const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogVerbose( szFormat, argptr );
  va_end( argptr );
}

#define IMPLEMENT_LOG_DEBUG_FUNCTION(level)                         \
  void wxVLog##level(const wxChar *szFormat, va_list argptr)        \
  {                                                                 \
    if ( wxLog::IsEnabled() ) {                                     \
      wxLog::OnLog(wxLOG_##level,                                   \
                   wxString::FormatV(szFormat, argptr), time(NULL));\
    }                                                               \
  }                                                                 \
  \
  void wxLog##level(const wxChar *szFormat, ...)                    \
  {                                                                 \
    va_list argptr;                                                 \
    va_start(argptr, szFormat);                                     \
    wxVLog##level(szFormat, argptr);                                \
    va_end(argptr);                                                 \
  }

void wxVLogTrace( const wxChar *mask, const wxChar *szFormat, va_list argptr ) {
  if( wxLog::IsEnabled() && wxLog::IsAllowedTraceMask( mask ) ) {
    wxString msg;
    msg << _T( "(" ) << mask << _T( ") " ) << wxString::FormatV( szFormat, argptr );
    wxLog::OnLog( wxLOG_Trace, msg, time( NULL ) );
  }
}

void wxLogTrace( const wxChar *mask, const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogTrace( mask, szFormat, argptr );
  va_end( argptr );
}

void wxVLogTrace( wxTraceMask mask, const wxChar *szFormat, va_list argptr ) {
  if( wxLog::IsEnabled() && ( ( wxLog::GetTraceMask() & mask ) == mask ) ) {
    wxLog::OnLog( wxLOG_Trace, wxString::FormatV( szFormat, argptr ), time( NULL ) );
  }
}

void wxLogTrace( wxTraceMask mask, const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogTrace( mask, szFormat, argptr );
  va_end( argptr );
}


IMPLEMENT_LOG_DEBUG_FUNCTION( Debug )
IMPLEMENT_LOG_DEBUG_FUNCTION( Trace )

static inline wxString wxLogSysErrorHelper( long err ) {
  return wxString::Format( _( " (error %ld: %s)" ), err, wxSysErrorMsg( err ) );
}

void wxVLogSysError( const wxChar *szFormat, va_list argptr ) {
  wxVLogSysError( wxSysErrorCode(), szFormat, argptr );
}

void wxLogSysError( const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogSysError( szFormat, argptr );
  va_end( argptr );
}

void wxVLogSysError( long err, const wxChar *fmt, va_list argptr ) {
  if( wxLog::IsEnabled() ) {
    wxLog::OnLog( wxLOG_Error, wxString::FormatV( fmt, argptr ) + wxLogSysErrorHelper( err ),
                  time( NULL ) );
  }
}

void wxLogSysError( long lErrCode, const wxChar *szFormat, ... ) {
  va_list argptr;
  va_start( argptr, szFormat );
  wxVLogSysError( lErrCode, szFormat, argptr );
  va_end( argptr );
}

wxCRIT_SECT_DECLARE( gs_prevCS );

unsigned wxLog::DoLogNumberOfRepeats() {
  wxLog * const pLogger = GetActiveTarget();
  return pLogger ? pLogger->LogLastRepeatIfNeeded() : 0u;
}

unsigned wxLog::LogLastRepeatIfNeeded() {
  wxCRIT_SECT_LOCKER( lock, gs_prevCS );
  return LogLastRepeatIfNeededUnlocked();
}

unsigned wxLog::LogLastRepeatIfNeededUnlocked() {
  long retval = ms_prevCounter;
  if( ms_prevCounter > 0 ) {
    wxString msg;
    msg.Printf( wxPLURAL( "The previous message repeated once.",
                          "The previous message repeated %lu times.",
                          ms_prevCounter ),
                ms_prevCounter );
    ms_prevCounter = 0;
    ms_prevString.clear();
    DoLog( ms_prevLevel, msg.c_str(), ms_prevTimeStamp );
  }
  return retval;
}

wxLog::~wxLog() {
}

void wxLog::OnLog( wxLogLevel level, const wxChar *szString, time_t t ) {
  if( IsEnabled() && ms_logLevel >= level ) {
    wxLog *pLogger = GetActiveTarget();
    if( pLogger ) {
      if( GetRepetitionCounting() ) {
        wxCRIT_SECT_LOCKER( lock, gs_prevCS );
        if( szString == ms_prevString ) {
          ms_prevCounter++;
          return;
        }
        pLogger->LogLastRepeatIfNeededUnlocked();
        ms_prevString = szString;
        ms_prevLevel = level;
        ms_prevTimeStamp = t;
      }
      pLogger->DoLog( level, szString, t );
    }
  }
}

wxChar *wxLog::SetLogBuffer( wxChar * ( buf ), size_t ( size ) ) {
  return NULL;
}

wxLog *wxLog::GetActiveTarget() {
  if( ms_bAutoCreate && ms_pLogger == NULL ) {
    static bool s_bInGetActiveTarget = false;
    if( !s_bInGetActiveTarget ) {
      s_bInGetActiveTarget = true;
      if( wxTheApp != NULL ) {
        ms_pLogger = wxTheApp->GetTraits()->CreateLogTarget();
      } else
      { ms_pLogger = new wxLogStderr; }
      s_bInGetActiveTarget = false;
    }
  }
  return ms_pLogger;
}

wxLog *wxLog::SetActiveTarget( wxLog *pLogger ) {
  if( ms_pLogger != NULL ) {
    ms_pLogger->Flush();
  }
  wxLog *pOldLogger = ms_pLogger;
  ms_pLogger = pLogger;
  return pOldLogger;
}

void wxLog::DontCreateOnDemand() {
  ms_bAutoCreate = false;
  ClearTraceMasks();
}

void wxLog::DoCreateOnDemand() {
  ms_bAutoCreate = true;
}

void wxLog::RemoveTraceMask( const wxString& str ) {
  int index = ms_aTraceMasks.Index( str );
  if( index != wxNOT_FOUND ) {
    ms_aTraceMasks.RemoveAt( ( size_t )index );
  }
}

void wxLog::ClearTraceMasks() {
  ms_aTraceMasks.Clear();
}

void wxLog::TimeStamp( wxString *str ) {
  if( ms_timestamp ) {
    wxChar buf[256];
    time_t timeNow;
    ( void )time( &timeNow );
    struct tm tm;
    wxStrftime( buf, WXSIZEOF( buf ),
                ms_timestamp, wxLocaltime_r( &timeNow, &tm ) );
    str->Empty();
    *str << buf << wxT( ": " );
  }
}

void wxLog::DoLog( wxLogLevel level, const wxChar *szString, time_t t ) {
  switch( level ) {
    case wxLOG_FatalError:
      DoLogString( wxString( _( "Fatal error: " ) ) + szString, t );
      DoLogString( _( "Program aborted." ), t );
      Flush();
      abort();
      break;
    case wxLOG_Error:
      DoLogString( wxString( _( "Error: " ) ) + szString, t );
      break;
    case wxLOG_Warning:
      DoLogString( wxString( _( "Warning: " ) ) + szString, t );
      break;
    case wxLOG_Info:
      if( GetVerbose() )
      case wxLOG_Message:
    case wxLOG_Status:
    default:    // log unknown log levels too
      DoLogString( szString, t );
      break;
    case wxLOG_Trace:
    case wxLOG_Debug: {
      wxString msg = level == wxLOG_Trace ? wxT( "Trace: " )
                     : wxT( "Debug: " );
      msg << szString;
      DoLogString( msg, t );
    }
    break;
  }
}

void wxLogBuffer::DoLog( wxLogLevel level, const wxChar *szString, time_t t ) {
  switch( level ) {
    case wxLOG_Trace:
    case wxLOG_Debug: {
      wxString str;
      TimeStamp( &str );
      str += szString;
      wxMessageOutputDebug dbgout;
      dbgout.Printf( _T( "%s\n" ), str.c_str() );
    }
    break;
    default:
      wxLog::DoLog( level, szString, t );
  }
}
void wxLog::DoLogString( const wxChar *szString, time_t t ) {
}

void wxLog::Flush() {
  LogLastRepeatIfNeeded();
}

bool wxLog::IsAllowedTraceMask( const wxChar *mask ) {
  for( wxArrayString::iterator it = ms_aTraceMasks.begin(),
       en = ms_aTraceMasks.end();
       it != en; ++it )
    if( *it == mask ) {
      return true;
    }
  return false;
}

void wxLogBuffer::Flush() {
  if( !m_str.empty() ) {
    wxMessageOutputBest out;
    out.Printf( _T( "%s" ), m_str.c_str() );
    m_str.clear();
  }
}

void wxLogBuffer::DoLogString( const wxChar *szString, time_t ( t ) ) {
  m_str << szString << _T( "\n" );
}

wxLogStderr::wxLogStderr( FILE *fp ) {
  if( fp == NULL ) {
    m_fp = stderr;
  } else
  { m_fp = fp; }
}

void wxLogStderr::DoLogString( const wxChar *szString, time_t ( t ) ) {
  wxString str;
  TimeStamp( &str );
  str << szString;
  wxFputs( str, m_fp );
  wxFputc( _T( '\n' ), m_fp );
  fflush( m_fp );
  if( m_fp == stderr ) {
    wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
    if( traits && !true ) {
      wxMessageOutputDebug dbgout;
      dbgout.Printf( _T( "%s\n" ), str.c_str() );
    }
  }
}

#include "wx/ioswrap.h"
wxLogStream::wxLogStream( wxSTD ostream *ostr ) {
  if( ostr == NULL ) {
    m_ostr = &wxSTD cerr;
  } else {
    m_ostr = ostr;
  }
}

void wxLogStream::DoLogString( const wxChar *szString, time_t t ) {
  wxString str;
  TimeStamp( &str );
  ( *m_ostr ) << wxSafeConvertWX2MB( str ) << wxSafeConvertWX2MB( szString ) << wxSTD endl;
}

wxLogChain::wxLogChain( wxLog *logger ) {
  m_bPassMessages = true;
  m_logNew = logger;
  m_logOld = wxLog::SetActiveTarget( this );
}

wxLogChain::~wxLogChain() {
  wxLog::SetActiveTarget( m_logOld );
  if( m_logNew != this ) {
    delete m_logNew;
  }
}

void wxLogChain::SetLog( wxLog *logger ) {
  if( m_logNew != this ) {
    delete m_logNew;
  }
  m_logNew = logger;
}

void wxLogChain::Flush() {
  if( m_logOld ) {
    m_logOld->Flush();
  }
  if( m_logNew && m_logNew != this ) {
    m_logNew->Flush();
  }
}

void wxLogChain::DoLog( wxLogLevel level, const wxChar *szString, time_t t ) {
  if( m_logOld && IsPassingMessages() ) {
    ( ( wxLogChain * )m_logOld )->DoLog( level, szString, t );
  }
  if( m_logNew && m_logNew != this ) {
    ( ( wxLogChain * )m_logNew )->DoLog( level, szString, t );
  }
}

wxLogPassThrough::wxLogPassThrough()
  : wxLogChain( this ) {
}

bool wxLog::ms_bRepetCounting = false;
wxString wxLog::ms_prevString;
unsigned int wxLog::ms_prevCounter = 0;
time_t wxLog::ms_prevTimeStamp = 0;
wxLogLevel wxLog::ms_prevLevel;

wxLog *wxLog::ms_pLogger      = ( wxLog * )NULL;
bool wxLog::ms_doLog        = true;
bool wxLog::ms_bAutoCreate  = true;
bool wxLog::ms_bVerbose     = false;

wxLogLevel wxLog::ms_logLevel     = wxLOG_Max;

size_t wxLog::ms_suspendCount = 0;

const wxChar *wxLog::ms_timestamp    = wxT( "%X" );

wxTraceMask wxLog::ms_ulTraceMask  = ( wxTraceMask )0;
wxArrayString wxLog::ms_aTraceMasks;

#ifdef LOG_PRETTY_WRAP
static void wxLogWrap( FILE *f, const char *pszPrefix, const char *psz ) {
  size_t nMax = 80;
  size_t nStart = strlen( pszPrefix );
  fputs( pszPrefix, f );
  size_t n;
  while( *psz != '\0' ) {
    for( n = nStart; ( n < nMax ) && ( *psz != '\0' ); n++ ) {
      putc( *psz++, f );
    }
    if( *psz != '\0' ) {
      /*putc('\n', f);*/
      for( n = 0; n < nStart; n++ ) {
        putc( ' ', f );
      }
      while( isspace( *psz ) ) {
        psz++;
      }
    }
  }
  putc( '\n', f );
}
#endif  //LOG_PRETTY_WRAP

unsigned long wxSysErrorCode() {
  return errno;
}

const wxChar *wxSysErrorMsg( unsigned long nErrCode ) {
  if( nErrCode == 0 ) {
    nErrCode = wxSysErrorCode();
  }
  static wchar_t s_wzBuf[1024];
  wxConvCurrent->MB2WC( s_wzBuf, strerror( ( int )nErrCode ), WXSIZEOF( s_wzBuf ) - 1 );
  return s_wzBuf;
}
